package server;

import control.CControl;
import entity.ERegister;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import websocket.Framedata;
import websocket.JSONMessage;

//Guillermo: I name "synchronized" all the methods that use both HashMaps.
public class SBroadcaster implements Observer
{
    private final static Logger LOGGER = Logger.getLogger(SBroadcaster.class
        .getName());
    
    private CControl cControl;
    
    // Two collections, one for clients with low priority and
    // another for the high priority ones, so that later we will be able
    // to limit sending response frequency to low priority clients to increase
    // performance for high priority clients
    private Collection<SClient> lowPriorityClients = new ArrayList<>();
    private Collection<SClient> highPriorityClients = new ArrayList<>();
    
    public SBroadcaster(CControl cControl)
    {
        this.cControl = cControl;
    }
    
    
    @Override
    public void update(Observable o, Object arg)
    {
        // Get the message, create broadcasting thread, give it message and clients
        
        // We will generate String response here, because thanks to it, we are sure
        // That the data we will take from EEntity are consistent, as the FDataStream
        // thread will be executing this code, so that it won't push new data
        // If we would do this in SBroadcastingThread then we can't be sure about
        // data consistency
        
        if (o instanceof SNewConnectionsHandler)
        {
            // Server status changed
            String status = (String) arg;
            switch (status)
            {
                case SNewConnectionsHandler.STATUS_RUNNING:
                    // Do nothing
                    break;
                case SNewConnectionsHandler.STATUS_STOPPED:
                    // Drop clients
                    Collection<SClient> clientsToSend = getClients();
                    if (clientsToSend.isEmpty())
                    {
                        // No clients to send the data
                        break;
                    }
            
                    Framedata closingFrame = Framedata.generateCloseFrame(
                            Framedata.ClosingReason.GOING_AWAY);
                    // Start broadcasting thread
                    SBroadcastingThread thread = new SBroadcastingThread(clientsToSend,
                            new Framedata[]{ closingFrame });
                    thread.start();
                    
                    // Remove clients from the list
                    lowPriorityClients.clear();
                    highPriorityClients.clear();
                    break;
            }
        }
        else
        {
            // New data available
            Collection<SClient> clientsToSend = getClients();
            if (clientsToSend.isEmpty())
            {
                // No clients to send the data
                return;
            }

            String[] nodes = cControl.getNodeList();
            Map<String, ERegister> registers;
            JSONMessage message = new JSONMessage("registers");

            for (String node : nodes)
            {
                registers = cControl.getRegisters(node);
                message.put(node, registers);
            }

            // Start broadcasting thread
            SBroadcastingThread thread = new SBroadcastingThread(clientsToSend, message.toJSONString());
            thread.start();
        }
    }

    private synchronized Collection<SClient> getClients()
    {
        // Get high priority clients
        Collection<SClient> clients = new ArrayList(highPriorityClients);

        // Get some of the low priority clients
        // TODO [If you need] implement fancy algorithm for decing which clients to choose for example depending on a available resources and internet connection
        clients.addAll(lowPriorityClients);

        return clients;
    }

    public synchronized void addClient(SClient client)
    {
        if (client == null)
        {
            LOGGER.log(Level.FINEST, "Null client passed");
            return; // Take no action
        }

        if (client.isHighPriority())
        {
            highPriorityClients.add(client);
            LOGGER.log(Level.INFO, "Client {0} was added to map with priority HIGH", client.getClientName());
        }
        else
        {
            lowPriorityClients.add(client);
            LOGGER.log(Level.INFO, "Client {0} was added to map with priority LOW", client.getClientName());
        }
    }

    public synchronized void removeClient(SClient client)
    {
        if (client == null)
        {
            LOGGER.log(Level.FINEST, "Null client passed");
            return; // Take no action
        }

        if (client.isHighPriority())
        {
            highPriorityClients.remove(client);
            LOGGER.log(Level.INFO, "Client {0} was removed from map with priority HIGH", client.getClientName());
        }
        else
        {
            lowPriorityClients.remove(client);
            LOGGER.log(Level.INFO, "Client {0} was removed from map with priority LOW", client.getClientName());
        }
        
    }
}
